﻿/*
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at 
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  
 */

using System;
using java = biz.ritter.javapi;

namespace biz.ritter.javapi.io
{

    /**
     * The base class for all readers. A reader is a means of reading data from a
     * source in a character-wise manner. Some readers also support marking a
     * position in the input and returning to this position later.
     * <p/>
     * This abstract class does not provide a fully working implementation, so it
     * needs to be subclassed, and at least the {@link #read(char[], int, int)} and
     * {@link #close()} methods needs to be overridden. Overriding some of the
     * non-abstract methods is also often advised, since it might result in higher
     * efficiency.
     * <p/>
     * Many specialized readers for purposes like reading from a file already exist
     * in this package.
     * 
     * @see Writer
     */
    public abstract class Reader : java.lang.Readable, Closeable {
        /**
         * The object used to synchronize access to the reader.
         */
        protected Object lockJ;

        /**
         * Constructs a new {@code Reader} with {@code this} as the object used to
         * synchronize critical sections.
         */
        protected Reader() : base (){
            lockJ = this;
        }

        /**
         * Constructs a new {@code Reader} with {@code lock} used to synchronize
         * critical sections.
         * 
         * @param lock
         *            the {@code Object} used to synchronize critical sections.
         * @throws NullPointerException
         *             if {@code lock} is {@code null}.
         */
        protected Reader(Object newLock) {
            if (newLock == null) {
                throw new java.lang.NullPointerException();
            }
            this.lockJ = newLock;
        }

        /**
         * Closes this reader. Implementations of this method should free any
         * resources associated with the reader.
         * 
         * @throws IOException
         *             if an error occurs while closing this reader.
         */
        public abstract void close();// throws IOException;

        /**
         * Sets a mark position in this reader. The parameter {@code readLimit}
         * indicates how many characters can be read before the mark is invalidated.
         * Calling {@code reset()} will reposition the reader back to the marked
         * position if {@code readLimit} has not been surpassed.
         * <p/>
         * This default implementation simply throws an {@code IOException};
         * subclasses must provide their own implementation.
         * 
         * @param readLimit
         *            the number of characters that can be read before the mark is
         *            invalidated.
         * @throws IllegalArgumentException
         *             if {@code readLimit &lt; 0}.
         * @throws IOException
         *             if an error occurs while setting a mark in this reader.
         * @see #markSupported()
         * @see #reset()
         */
        public virtual void mark(int readLimit)// throws IOException 
        {
            throw new java.io.IOException();
        }

        /**
         * Indicates whether this reader supports the {@code mark()} and
         * {@code reset()} methods. This default implementation returns
         * {@code false}.
         * 
         * @return always {@code false}.
         */
        public virtual bool markSupported() {
            return false;
        }

        /**
         * Reads a single character from this reader and returns it as an integer
         * with the two higher-order bytes set to 0. Returns -1 if the end of the
         * reader has been reached.
         * 
         * @return the character read or -1 if the end of the reader has been
         *         reached.
         * @throws IOException
         *             if this reader is closed or some other I/O error occurs.
         */
        public virtual int read() //throws IOException 
        {
            lock (lockJ) {
                char [] charArray = new char[1];
                if (read(charArray, 0, 1) != -1) {
                    return charArray[0];
                }
                return -1;
            }
        }

        /**
         * Reads characters from this reader and stores them in the character array
         * {@code buf} starting at offset 0. Returns the number of characters
         * actually read or -1 if the end of the reader has been reached.
         * 
         * @param buf
         *            character array to store the characters read.
         * @return the number of characters read or -1 if the end of the reader has
         *         been reached.
         * @throws IOException
         *             if this reader is closed or some other I/O error occurs.
         */
        public virtual int read(char [] buf) //throws IOException 
        {
            return read(buf, 0, buf.Length);
        }

        /**
         * Reads at most {@code count} characters from this reader and stores them
         * at {@code offset} in the character array {@code buf}. Returns the number
         * of characters actually read or -1 if the end of the reader has been
         * reached.
         * 
         * @param buf
         *            the character array to store the characters read.
         * @param offset
         *            the initial position in {@code buffer} to store the characters
         *            read from this reader.
         * @param count
         *            the maximum number of characters to read.
         * @return the number of characters read or -1 if the end of the reader has
         *         been reached.
         * @throws IOException
         *             if this reader is closed or some other I/O error occurs.
         */
        public abstract int read(char[] buf, int offset, int count);
                //throws IOException;

        /**
         * Indicates whether this reader is ready to be read without blocking.
         * Returns {@code true} if this reader will not block when {@code read} is
         * called, {@code false} if unknown or blocking will occur. This default
         * implementation always returns {@code false}.
         * 
         * @return always {@code false}.
         * @throws IOException
         *             if this reader is closed or some other I/O error occurs.
         * @see #read()
         * @see #read(char[])
         * @see #read(char[], int, int)
         */
        public virtual bool ready() //throws IOException
        {
            return false;
        }

        /**
         * Resets this reader's position to the last {@code mark()} location.
         * Invocations of {@code read()} and {@code skip()} will occur from this new
         * location. If this reader has not been marked, the behavior of
         * {@code reset()} is implementation specific. This default
         * implementation throws an {@code IOException}.
         * 
         * @throws IOException
         *             always thrown in this default implementation.
         * @see #mark(int)
         * @see #markSupported()
         */
        public virtual void reset() //throws IOException 
        {
            throw new java.io.IOException();
        }

        /**
         * Skips {@code amount} characters in this reader. Subsequent calls of
         * {@code read} methods will not return these characters unless {@code
         * reset()} is used. This method may perform multiple reads to read {@code
         * count} characters.
         * 
         * @param count
         *            the maximum number of characters to skip.
         * @return the number of characters actually skipped.
         * @throws IllegalArgumentException
         *             if {@code amount &lt; 0}.
         * @throws IOException
         *             if this reader is closed or some other I/O error occurs.
         * @see #mark(int)
         * @see #markSupported()
         * @see #reset()
         */
        public virtual long skip(long count) //throws IOException
        {
            if (count < 0) {
                throw new java.lang.IllegalArgumentException();
            }
            lock (lockJ) {
                long skipped = 0;
                int toRead = count < 512 ? (int) count : 512;
                char []charsSkipped = new char[toRead];
                while (skipped < count) {
                    int read = this.read(charsSkipped, 0, toRead);
                    if (read == -1) {
                        return skipped;
                    }
                    skipped += read;
                    if (read < toRead) {
                        return skipped;
                    }
                    if (count - skipped < toRead) {
                        toRead = (int) (count - skipped);
                    }
                }
                return skipped;
            }
        }

        /**
         * Reads characters and puts them into the {@code target} character buffer.
         * 
         * @param target
         *            the destination character buffer.
         * @return the number of characters put into {@code target} or -1 if the end
         *         of this reader has been reached before a character has been read.
         * @throws IOException
         *             if any I/O error occurs while reading from this reader.
         * @throws NullPointerException
         *             if {@code target} is {@code null}.
         * @throws ReadOnlyBufferException
         *             if {@code target} is read-only.
         */
        public virtual int read(java.nio.CharBuffer target)// throws IOException 
        {
            if (null == target) {
                throw new java.lang.NullPointerException();
            }
            int length = target.length();
            char[] buf = new char[length];
            length = java.lang.Math.min(length, read(buf));
            if (length > 0) {
                target.put(buf, 0, length);
            }
            return length;
        }
    }
}
